#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "termino_merge.h"
#include "abb_avl.h"

// Cantidad de digitos de la maxima frecuencia
#define MAX_APARICION 20

/** ***********							*********** **
 ** ***********	FUNCIONES AUXILIARES	*********** **
 ** ***********							*********** **/

/*
 * Funcion auxiliar para incrementar enteros con puntero.
 * No devuelve nada.
 */
void incrementar_entero_merge(int* entero,int cantidad){
	*entero = *entero + cantidad;
}

/** ***********									*********** **
 ** ***********	DEFINICION DEL TIPO DE DATOS	*********** **
 ** ***********									*********** **/	

/*
 * Estructura auxiliar contenedora del string del termino
 * y las apariciones en cada documento, necesaria para la comparación
 * y actualización de datos en distintos archivos.
 */
struct termino_merge{
	
	char* string;
	
	abb_avl_t* apariciones;
	
	int id;
		
	int tamanio;
	
};

/*
 * Crea un nuevo termino a partir del string recibido por parámetro.
 * Actualiza su tamanio y crea su AVL. Lo devuelve al terminar
 * de hacer esto.
 */
termino_merge_t* termino_merge_crear(char* string){
	
	termino_merge_t* nuevo = (termino_merge_t*) malloc(sizeof(termino_merge_t));
	
	nuevo->apariciones = avl_crear(strcmp,free);
	
	nuevo->string = (char*) malloc( (strlen(string)+1) * sizeof(char) );
	
	sprintf(nuevo->string,"%s",string);
	
	nuevo->tamanio = sizeof(termino_merge_t) + strlen(string) + sizeof(char);
	
	nuevo->id = -1;
	
	return nuevo;
	
}

/*
 * Destruye el término recibido por parámetro.
 */
void termino_merge_destruir(termino_merge_t* term){
		
	if( !term ) return;
	
	avl_destruir(term->apariciones);
	
	free(term->string);
	
	free(term);
	
}

/*
 * Devuelve el string asociado a un término.
 */
char* termino_merge_obtener_string(termino_merge_t* term){
	
	if( !term ) return NULL;
	
	return term->string;
	
}

/*
 * Devuelve el tamanio del término recibido por parámetro.
 */
int termino_merge_ver_tamanio(termino_merge_t* term){
	
	return term->tamanio;

}

/*
 * Devuelve el id del termino;
 */
int termino_merge_obtener_id(termino_merge_t* term){
	
	return term->id;
	
}

/*
 * Setea el id del termino recibido al valor recibido.
 */
void termino_merge_set_id(termino_merge_t* term, int id){
	
	term->id = id;
	
}

/*
 * Devuelve el AVL de Documento-Apariciones del termino
 * recibido.
 */
abb_avl_t* termino_merge_obtener_apariciones(termino_merge_t* term){
	
	if(!term) return NULL;
		
	return term->apariciones;
	
}

/*
 * Agrega las apariciones del termino recibido en el documento recibido.
 * Actualiza el tamanio que ocupa el término en memoria.
 */
void termino_merge_agregar_apariciones(termino_merge_t* term,char* documento,int apariciones){
	
	int* repeticiones = NULL;
	
	if (!avl_pertenece(term->apariciones,documento)){
		
		term->tamanio += strlen(documento) + 1 + sizeof(int);
		repeticiones = (int*) malloc( sizeof(int) );
		*repeticiones = apariciones;
		avl_guardar(term->apariciones,documento,repeticiones);
		return;
		
	}
	
	repeticiones = (int*)avl_obtener(term->apariciones,documento);
	incrementar_entero_merge(repeticiones,apariciones);
	
}

/*
 * Devuelve la cantidad de apariciones del termino recibido
 * en el documento recibido. Devuelve -1 si no recibe documento.
 * Devuelve 0 si el término no aparece en el documento.
 */
int termino_merge_obtener_apariciones_en(termino_merge_t* term,char* documento){
	
	if( !term || !documento ) return -1;
	
	if( !avl_pertenece(term->apariciones,documento) ) return 0;
	
	int* puntero_aparicion = avl_obtener(term->apariciones,documento);
	
	return *puntero_aparicion;
	
}

/*
 * Combina dos términos de igual nombre en uno solo.
 * Recibe un término fuente y uno destino, en el cual se copiarán
 * las apariciones de los documentos.
 */
void combinar_terminos(termino_merge_t* destino,termino_merge_t* fuente){

	if( strcmp(destino->string,fuente->string)!=0 ) return;
	
	avl_iter_t* iterador_fuente = avl_iter_in_crear(fuente->apariciones);

	char* documento_actual = NULL;
	
	while( !avl_iter_in_al_final(iterador_fuente) ){
		
		documento_actual = (char*)avl_iter_in_ver_actual(iterador_fuente);
		
		termino_merge_agregar_apariciones( destino,documento_actual,termino_merge_obtener_apariciones_en(fuente,documento_actual) );
		
		avl_iter_in_avanzar(iterador_fuente);
		
	}
	
	avl_iter_in_destruir(iterador_fuente);
	
	termino_merge_destruir(fuente);
	
}


/*
 * Vuelca el avl a un subarchivo, con el formato que discutimos (termino,repeticiones).
 */
int escribir_termino_merge_a_archivo(FILE* destino, termino_merge_t* term){
	
	int numero_de_documentos = 0;
	int apariciones = -1;
	unsigned long int suma_frecuencia_al_cuadrado = 0;
	char* documento_actual = NULL;
	char* nombre_entrada = termino_merge_obtener_string(term);
	abb_avl_t* avl = termino_merge_obtener_apariciones(term);
	avl_iter_t* iterador = avl_iter_in_crear(avl);
	fprintf(destino,"%s",nombre_entrada);
	
	while( !avl_iter_in_al_final(iterador) ){
		
		char* str_apariciones = (char*) malloc( (MAX_APARICION+1)*sizeof(char) );
		documento_actual = (char*)avl_iter_in_ver_actual(iterador);
		apariciones = *(int*)avl_obtener(avl,documento_actual);
		sprintf(str_apariciones,"%d",apariciones);
		fprintf(destino,",%s,%s",documento_actual,str_apariciones);
		avl_iter_in_avanzar(iterador);
		free(str_apariciones);
		numero_de_documentos++;
		suma_frecuencia_al_cuadrado += apariciones*apariciones;
		
	}
	
	avl_iter_in_destruir(iterador);
	
	/* 
	 * Necesito la cantidad de documentos en los que aparece el
	 * termino para el campo "CantidadValoresDistintosDeCero" de
	 * la matriz del SVDLIBC.
	 */
	fprintf(destino,"\n%d\n%lu\n",numero_de_documentos,suma_frecuencia_al_cuadrado);
	return numero_de_documentos;

}

/*
 //Estructura para copiar los indices a binario; 2 unsigned long: puntero a termino e indice 
  
 typedef struct puntero_a_termino_e_indice{
    unsigned long int ptr, indice;

}puntero_a_termino_e_indice_t;

*/

unsigned long int escribir_termino_a_indice(FILE* indice,FILE* archivo_terminos,termino_merge_t* term,unsigned long int numero_termino,unsigned long int ultima_posicion){
	
	int tamanio_termino = strlen(termino_merge_obtener_string(term));
	
	unsigned long int puntero=ultima_posicion;
	//Copio el pointer al stream
	fwrite(&puntero,sizeof(unsigned long int),1,indice);
	
	//Copio el termino al archivo de lexico
	fprintf(archivo_terminos,"%s",termino_merge_obtener_string(term));
	
	//Devuelvo puntero al inicio del siguiente termino
	return ( (unsigned long int)tamanio_termino + ultima_posicion);
	
}
